package com.mofeng.myshop.service.user.impl;

import com.mofeng.myshop.constants.UserConstant;
import com.mofeng.myshop.domain.po.RefreshToken;
import com.mofeng.myshop.domain.po.UserBase;
import com.mofeng.myshop.domain.vo.LoginStateVo;
import com.mofeng.myshop.domain.vo.user.UserVo;
import com.mofeng.myshop.mapper.RefreshTokenMapper;
import com.mofeng.myshop.mapper.UserBaseMapper;
import com.mofeng.myshop.service.user.EmailService;
import com.mofeng.myshop.service.user.UserUtilsService;
import com.mofeng.myshop.until.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.TextUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Random;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserUtilsServiceImpl implements UserUtilsService {
    private final UserBaseMapper userMapper;

    private final EmailService emailService;

    private final SessionServiceImpl sessionUtil;

    private final RefreshTokenMapper refreshTokenMapper;

    private final SnowflakeIdWorker idWorker;


    /**
     * 查看图灵码验证状态，验证一次后，五分钟内不用重复输入验证码
     */
    @Override
    public Boolean checkCaptchaState() {
        Object content = sessionUtil.getCaptchaCheckState(sessionUtil.getCookieCaptchaKey());
        if (MyUtil.StringUtil.isEmpty(content)) {
            throw new RuntimeException("请先验证图灵码后操作");
        }
        return true;
    }

    /**
     * 用户修改密码时，邮箱验证
     *
     * @param email     邮箱
     * @param emailCode 邮箱验证码
     */
    @Override
    public R checkEmailCode(String email, String emailCode) {
        String emailRedisCode = sessionUtil.getEmailCodeContent(email);
        if (!emailCode.equals(emailRedisCode)) {
            return R.FAILED("邮箱验证码不正确");
        }
        return R.SUCCESS("验证码正确");
    }

    /**
     * 判断图灵验证码是否有效
     *
     * @param captchaCode 验证码
     */
    @Override
    public R isCaptchaCodeRight(String captchaCode) {
        getAndCheckCaptchaContent(captchaCode);
        return R.SUCCESS("图灵验证码正确！");
    }

    /**
     * 手机号发送验证码
     *
     * @param type  修改类型
     * @param phone 手机号
     */
    @Override
    public R sendPhone(String type, String phone) {
        //检查是否是人为操作，是否验证了图灵码
        checkCaptchaState();
        //检查手机号是否正确
        boolean phoneOk = MyUtil.RegularUtil.isPhoneOk(phone);
        if (!phoneOk || TextUtils.isEmpty(phone)) {
            return R.FAILED("手机号格式不正确！");
        }
        //根据类型判断邮箱是否符合要求
        if (UserConstant.Email.REGISTER_KEY.equals(type) || UserConstant.Email.UPDATE_KEY.equals(type)) {
            //注册register:如果已经注册过，就提示说，该手机号已经注册
            //修改手机号update:如果已经注册了，提示改手机号已经注册
            if (checkPhone(phone)) {
                return R.FAILED("手机号已注册！");
            }
        } else if (UserConstant.Email.FORGET_KEY.equals(type)) {
            //找密码 forget :如果没有注册过。提示该手机号没有注册
            if (!checkPhone(phone)) {
                return R.FAILED("手机号未注册！");
            }
        }
        //防止暴力发送，间隔要超过30秒发1次。同一个Ip，10分钟最多只能发10次(如果是短信，只能发5次）
        String remoteAddr = ServletUtils.getUserIp();
        if (remoteAddr != null) {
            remoteAddr = remoteAddr.replaceAll(":", "_");
        }
        Integer ipSendTimes = sessionUtil.checkEmailSendIp(remoteAddr);
        Object hasSendEmail = sessionUtil.getEmailCodeContent(phone);
        if (hasSendEmail != null) {
            return R.FAILED("操作频繁，请稍后重试!");
        }
        //发送验证码
        String code = String.valueOf(new Random().nextInt(899999) + 100000);
        try {
            emailService.sendPhoneVerifyCode(code, phone, type);
        } catch (Exception e) {
            return R.FAILED("发送失败，请稍后重试！");
        }
        //做记录
        sessionUtil.addEmailSendIp30(remoteAddr);
        sessionUtil.setEmailCodeContent(phone, code);
        return R.SUCCESS("验证码发送成功！请查收！");
    }

    /**
     * 邮箱发送验证码
     *
     * @param type         获取邮箱验证码类型
     * @param emailAddress 邮箱
     */
    @Override
    public R sendEmailCode(String type, String emailAddress) {
        //检查是否是人为操作，是否验证了图灵码
        checkCaptchaState();
        //检查邮箱地址是否正确
        if (TextUtils.isEmpty(emailAddress)) {
            return R.FAILED("邮箱地址不能为空~");
        }
        boolean emailIsOk = MyUtil.RegularUtil.isEmailAddresOk(emailAddress);
        if (!emailIsOk) {
            return R.FAILED("邮箱地址格式不正确~");
        }
        //根据类型判断邮箱是否符合要求
        if (UserConstant.Email.REGISTER_KEY.equals(type) || UserConstant.Email.UPDATE_KEY.equals(type)) {
            //注册register:如果已经注册过，就提示说，该邮箱已经注册
            //修改邮箱(新的邮箱)update:如果已经注册了，提示改邮相已经注册
            if (checkEmail(emailAddress)) {
                return R.FAILED("邮箱已注册~");
            }
        } else if (UserConstant.Email.FORGET_KEY.equals(type)) {
            //找网密码 forget :如果没有注册过。提示该邮箱没有注册
            if (!checkEmail(emailAddress)) {
                return R.FAILED("邮箱未注册~");
            }
        }
        //防止暴力发送，间隔要超过30秒发1次。同一个Ip，10分钟最多只能发10次(如果是短信，只能发5次）
        String remoteAddr = ServletUtils.getUserIp();
        if (remoteAddr != null) {
            remoteAddr = remoteAddr.replaceAll(":", "_");
        }
        sessionUtil.checkEmailSendIp(remoteAddr);
        //发送验证码
        String code = String.valueOf(new Random().nextInt(899999) + 100000);
        try {
            emailService.sendEmailVerifyCode(code, emailAddress, type);
        } catch (Exception e) {
            return R.FAILED("发送失败，请稍后重试~");
        }
        //4.做记录
        sessionUtil.addEmailSendIp30(remoteAddr);
        sessionUtil.setEmailCodeContent(emailAddress, code);
        return R.SUCCESS("验证码发送成功,请查收~");
    }

    /**
     * 给用户发送邮件
     *
     * @param from         发件人
     * @param title        邮件标题
     * @param content      邮件内容
     * @param emailAddress 收件人
     */
    @Override
    public void sendEmailUser(String from, String title, String content, String emailAddress) {
        try {
            checkEmail(title, content, emailAddress);
            emailService.sendEmail(from, title, content, emailAddress);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 给用户发送邮件
     *
     * @param title        邮件标题
     * @param content      邮件内容
     * @param emailAddress 收件人
     */
    @Override
    public void sendEmailAdmin(String title, String content, String emailAddress) {
        try {
            checkEmail(title, content, emailAddress);
            emailService.sendEmail(title, content, emailAddress);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void checkEmail(String title, String content, String emailAddress) {
        if (MyUtil.StringUtil.isEmpty(title)) {
            throw new RuntimeException("邮件标题不能为空");
        }
        if (MyUtil.StringUtil.isEmpty(content)) {
            throw new RuntimeException("邮件内容不能为空");
        }
        if (MyUtil.StringUtil.isEmpty(emailAddress)) {
            throw new RuntimeException("邮件发件人不能为空");
        }
    }

    /**
     * 检查邮箱或手机验证码状态（防止机器爆力发送）
     *
     * @return 正常返回true
     */
    private boolean checkEmailCode(String userName) {
        LoginStateVo loginStateVo = new LoginStateVo(ServletUtils.getUserIp(), userName);
        try {
            sessionUtil.checkSendEmailCodeCount(loginStateVo);
        } catch (Exception e) {
            emailService.sendEmailWarning(loginStateVo);
            throw new RuntimeException(e);
        }
        return true;
    }


    /**
     * 通过携带的TokenKey查看是否有此用户登录；登陆：返回用户信息
     *
     * @return 登录的用户信息
     */
    @Override
    @Transactional
    public UserVo parseToken() {
        String tokenKey = sessionUtil.getCookieTokenKey();
        UserVo userVo = parseByTokenKey(tokenKey);
        String from = checkFrom(tokenKey);
        if (userVo == null) {
            //解析出错（过期）,去数据库中查询refreshToken
            RefreshToken refreshToken = getTokenByKeyAndForm(tokenKey);
            if (refreshToken == null) {
                //不存在，则当前用户未登录
                return null;
            }
            //解析refreshToken,看看是否过期
            UserVo user = parseTokenByFrom(refreshToken, from);
            if (user == null) {
                //解析失败，（过期）重新登录
                return null;
            }
            //创建新的refreshToken  根据UserId查询对应user
            UserBase userFromDb = userMapper.findAllInfoById(MyUtil.NumberUtil.asLong(refreshToken.getUserId()));
            String newTokenKey = createToken(userFromDb, from);//创建新的token
            userVo = parseByTokenKey(newTokenKey);
        }
        return userVo;
    }

    private UserVo parseTokenByFrom(RefreshToken refreshToken, String from) {
        UserVo user;
        if (Constants.FROM_PC.equals(from)) {
            user = JwtUtil.parseToken(refreshToken.getPcToken());
        } else {
            user = JwtUtil.parseToken(refreshToken.getMobileToken());
        }
        return user;
    }

    /**
     * 根据TokenKey获取数据库的RefreshToken对象
     *
     * @param tokenKey TokenKey
     */
    private RefreshToken getTokenByKeyAndForm(String tokenKey) {
        //解析是什么端登录
        String from = checkFrom(tokenKey);
        RefreshToken refreshToken;
        if (Constants.FROM_PC.equals(from)) {
            refreshToken = refreshTokenMapper.findByPcTokenKey(tokenKey);
        } else {
            refreshToken = refreshTokenMapper.findByMobileTokenKey(tokenKey);
        }
        return refreshToken;
    }

    /**
     * 获取且检查邮箱验证码对不对邮箱码
     */
    @Override
    public <T> T getAndCheckEmailCode(String email, String emailCodePortal) {
        Object emailCodeRedis = sessionUtil.getEmailCodeContent(email);
        if (MyUtil.StringUtil.isEmpty(emailCodeRedis) || !emailCodePortal.equals(emailCodeRedis)) {
            throw new RuntimeException("邮箱验证码错误，请重试");
        }
        return (T) emailCodeRedis;
    }

    /**
     * 通过tokenKey解析token
     *
     * @param tokenKey tokenKey
     * @return 用户信息 UserVo
     */
    private UserVo parseByTokenKey(String tokenKey) {
        if (TextUtils.isEmpty(tokenKey)) {
            return null;
        }
        String token = sessionUtil.getToken(tokenKey);
        if (token != null) {
            return JwtUtil.parseToken(token);
        }
        return null;
    }

    /**
     * 获取图灵验证码
     */
    @Override
    public void getCaptchaCode() {
        String key;
        String lastId = sessionUtil.getCookieCaptchaKey();
        if (MyUtil.StringUtil.isEmpty(lastId)) {
            key = idWorker.nextId() + "";
        } else {
            key = lastId;
        }
        sessionUtil.setCookieCaptchaKey(key);
        String content = CaptchaCodeUtil.code();
        //保存到redis
        sessionUtil.setCaptchaContent(key, content);
    }

    /**
     * 生成token
     *
     * @param user 用户信息
     * @param from 来源
     * @return token的MD5值
     */
    @Transactional
    @Override
    public String createToken(UserBase user, String from) {
        UserVo userVo = new UserVo(user);
        //确保单端登录,删除之前的旧的Token
        deleteOldToken(userVo, false);
        // 生成token
        String token = JwtUtil.createToken(userVo);
        //返回token的MD5值，token保存到redis中
        String tokenKey = from + MyUtil.MD5Util.calculateMD5(token);
        sessionUtil.setToken(tokenKey, token);
        //把tokenKey存在客户端的cookie中
        sessionUtil.setCookieTokenKey(tokenKey);
        //判断数据库中有没有，没有的话就创建，有就修改
        RefreshToken refreshToken = refreshTokenMapper.findByUserId(user.getId());
        if (refreshToken == null) {
            //生成refreshToken存到数据库
            refreshToken = new RefreshToken();
            refreshToken.setUserId(user.getId().toString());
            refreshToken.setId(idWorker.nextId() + "");
            refreshTokenMapper.insert(refreshToken);
        }
        //根据来源设置tokenKey
        if (Constants.FROM_PC.equals(from)) {
            refreshToken.setPcTokenKey(tokenKey);
            refreshToken.setPcToken(token);
        } else {
            refreshToken.setMobileTokenKey(tokenKey);
            refreshToken.setMobileToken(token);
        }
        refreshTokenMapper.updateById(refreshToken);
        return tokenKey;
    }

    /**
     * 检查邮箱是否存在
     *
     * @param email 邮箱
     * @return false 表示没有
     */
    @Override
    public boolean checkEmail(String email) {
        UserBase hasEmail = userMapper.findByEmail(email);
        return hasEmail != null && !Constants.State.DELETE_STATE.equals(hasEmail.getState());
    }

    /**
     * 检查用户名是否存在
     *
     * @param userName 用户名
     * @return false 表示没有
     */
    @Override
    public boolean checkUserName(String userName) {
        UserBase hasUser = userMapper.loginFindByUserName(userName);
        return hasUser != null && !Constants.State.DELETE_STATE.equals(hasUser.getState());
    }

    /**
     * 检查手机号是否存在
     *
     * @param phone 手机号
     * @return false 没有
     */
    @Override
    public boolean checkPhone(String phone) {
        UserBase hasEmail = userMapper.findByPhone(phone);
        return hasEmail != null && !Constants.State.DELETE_STATE.equals(hasEmail.getState());
    }

    /**
     * 判断登录端
     *
     * @param tokenKey 客户端存储的TokenKey
     */
    private String checkFrom(String tokenKey) {
        if (MyUtil.StringUtil.isEmpty(tokenKey)) {
            return "";
        }
        return tokenKey.startsWith(Constants.FROM_MOTILE) ? Constants.FROM_MOTILE : Constants.FROM_PC;
    }

    /**
     * 从缓存中获取且校验图灵验证码
     *
     * @param captchaCode 用户输入的图灵码
     */
    @Override
    public <T> T getAndCheckCaptchaContent(String captchaCode) {
        String captchaKey = sessionUtil.getCookieCaptchaKey();
        Object redisCaptchaCode = sessionUtil.getCaptchaContent(captchaKey);
        if (MyUtil.StringUtil.isEmpty(redisCaptchaCode)) {
            sessionUtil.delCaptchaCheckState(captchaKey);
            throw new RuntimeException("验证码无效或过期！");
        }
        captchaCode = captchaCode.toLowerCase();
        if (!redisCaptchaCode.equals(captchaCode)) {
            sessionUtil.delCaptchaCheckState(captchaKey);
            throw new RuntimeException("图灵验证码错误！");
        }
        sessionUtil.setCaptchaCheckState(captchaKey);
        return (T) redisCaptchaCode;
    }

    /**
     * 获取登录端
     */
    @Override
    public String getFrom() {
        String tokenKey = sessionUtil.getCookieTokenKey();
        return checkFrom(tokenKey);
    }

    /**
     * 检查登录状态（防止机器爆破密码）
     */
    @Override
    public void checkLoginCount(String userName) {
        LoginStateVo loginStateVo = new LoginStateVo(ServletUtils.getUserIp(), userName);
        try {
            sessionUtil.checkLoginCount(loginStateVo);
        } catch (Exception e) {
            emailService.sendEmailWarning(loginStateVo);
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 删除用户验证信息
     *
     * @param emailOrPhone 邮箱/手机
     */
    @Override
    public void removeValidateMsg(String emailOrPhone) {
        sessionUtil.delEmailCodeContent(emailOrPhone);
        String captchaKey = sessionUtil.getCookieCaptchaKey();
        sessionUtil.delCaptchaContent(captchaKey);
        sessionUtil.delCookieCaptchaKey();
        sessionUtil.removeLoginCount();
    }

    /**
     * 删除用户的旧的Token信息
     *
     * @param user  用户信息
     * @param isAll 是否所有端的用户登录信息都删除
     */
    @Transactional
    public void deleteOldToken(UserVo user, boolean isAll) {
        if (user == null){
            return;
        }
        RefreshToken refreshTokenFromDB = refreshTokenMapper.findByUserId(MyUtil.NumberUtil.asLong(user.getId()));
        String tokenKey = sessionUtil.getCookieTokenKey();
        if (refreshTokenFromDB != null) {
            if (isAll) {
                String pcTokenKey = refreshTokenFromDB.getPcTokenKey();
                String mobileTokenKey = refreshTokenFromDB.getMobileTokenKey();
                sessionUtil.delToken(pcTokenKey);
                sessionUtil.delToken(mobileTokenKey);
                sessionUtil.delEmailCodeContent(user.getEmail());
                sessionUtil.delEmailCodeContent(user.getPhone());
                sessionUtil.delCookieCaptchaKey();
                refreshTokenMapper.deleteById(refreshTokenFromDB);
            } else {
                if (Constants.FROM_MOTILE.equals(checkFrom(tokenKey))) {
                    refreshTokenFromDB.setMobileTokenKey(" ");
                    refreshTokenFromDB.setMobileToken(" ");
                } else {
                    refreshTokenFromDB.setPcTokenKey(" ");
                    refreshTokenFromDB.setPcToken(" ");
                }
                sessionUtil.delToken(tokenKey);
                refreshTokenMapper.updateById(refreshTokenFromDB);
            }
        }
        sessionUtil.removeLoginCount();
    }

}
